XRP Button Drive
Overview
This tutorial builds on the XRP Tank Drive tutorial. If you have not completed that one, please do so before continuing.
As you might have noticed in tank drive tutorial, driving the robot using the left and right joystick to control the power to each wheel can be difficult to control. Let's try to make something a little easier to control.
Button drive is a control scheme where specific buttons on an Xbox controller are used to control the robot's movement. For example:
- Pressing the Y button drives the robot forward.
- Pressing the A button drives the robot backward.
- Pressing the X button turns the robot left.
- Pressing the B button turns the robot right.
If you have some experience programming, try implementing this drive control now. Otherwise, read on for step-by-step instructions.
The Pre-Code Workout 📊
Before writing any code, let's break down the tasks into a flow chart. This helps us visualize the steps needed to implement button-based driving.
Let's start with listing out the tasks we need to perform:
- Drive forward when the Y button is pressed.
- Drive backward when the A button is pressed.
- Turn left when the X button is pressed.
- Turn right when the B button is pressed.
Flow Chart:
Now lets make a [Flow Chart](../../../CPP Docs/CPP_software_quick_reference/index.md#flow-charts). Try this on your own before looking at the flow chart
Flow Chart 📊
Inputs and Outputs
Now that we understand how the code will work we need to define the input and output of our function. It is important to also define the [data types](../../../CPP Docs/CPP_software_quick_reference/index.md#variables-and-data-types) for these inputs and outputs.
Define Inputs and Outputs. Try defining the Inputs and Outputs before looking.
Inputs:
forward
: Indicating if the forward button is pressed (Y): Datatype (bool).backward
: Indicating if the backward button is pressed (A): Datatype (bool).turnLeft
: Indicating if the turn left button is pressed (X): Datatype (bool).turnRight
: Indicating if the turn right button is pressed (B): Datatype (bool).
Outputs:
Left motor power
: Left motor speed: Datatype (double).Right motor power
: Right motor speed: Datatype (double).
Time to Start Coding
Creating a project
If you haven't already created an XRP project, you'll need to do that now. See [How to Create an XRP Project](../../../XRP Docs/03_XRP_project/index.md) If you have, it's time to start coding!
Create a Drivetrain Subsystem
The first step is to create a subsystem for our drivetrain. See How to Create a Subsystem for instructions on how to do this. You should name your subsystem Drivetrain
.
Drivetrain.h Header File
Just as a reminder, the header (.h
) file is like a table of contents for our code. It declares the functions that are available, but the actual instructions for those functions are written in the source (.cpp
) file. We are about to add a new function declaration to our Drivetrain.h
.
-
We need to tell the software we want to use the XRP robot motors. To do this we will need to include the header that declares the motor objects, see [Controlling a Motor](../WPILib VSCode Docs/tutorial_XRP_WPILib.md#controlling-a-motor) for more details on controlling XRP robot motor.
- We will need to add the following include to the top of the
Drivetrain.h
file
#include <frc/xrp/XRPMotor.h>
- We will need to add the following include to the top of the
-
Now we need to tell our code about the two motors on the robot.
- Let's add the code to create these motor objects inside our
Drivetrain
class. We'll put them in theprivate
section ofDrivetrain.h
. Making themprivate
means only theDrivetrain
code can talk to the motors directly, which helps keep our project organized.
// This creates an object for the left motor on channel 0
frc::XRPMotor m_left_motor{0};
// This creates an object for the right motor on channel 1
frc::XRPMotor m_right_motor{1}; - Let's add the code to create these motor objects inside our
-
We will be adding in a ButtonDrive function in the public section.
// A function to drive the robot with button drive controls.
// It takes button press and turns them into left and right motor speeds
void ButtonDrive(bool forward, bool backward, bool turnLeft, bool turnRight);
What does this declaration mean?
void
: This is the function's return type.void
means this function does not return any value after it runs; it only performs actions.ButtonDrive
: This is the name we've given our function.(bool forward, ...)
: The parentheses contain the function's parameters (the inputs). Each parameter has a data type and a name.bool forward
: A parameter namedforward
that expects abool
(true/false) value.bool backward
: A parameter namedbackward
that expects abool
(true/false) value.bool turnLeft
: A parameter namedturnLeft
that expects abool
(true/false) value.bool turnRight
: A parameter namedturnRight
that expects abool
(true/false) value.
Your Drivetrain.h file should look like this.
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <frc2/command/SubsystemBase.h>
#include <frc/xrp/XRPMotor.h>
class Drivetrain : public frc2::SubsystemBase {
public:
Drivetrain();
// A function to drive the robot with button drive controls.
// It takes button press and turns them into left and right motor speeds
void ButtonDrive(bool forward, bool backward, bool turnLeft, bool turnRight);
/**
* Will be called periodically whenever the CommandScheduler runs.
*/
void Periodic() override;
private:
// Components (e.g. motor controllers and sensors) should generally be
// declared private and exposed only through public methods.
// This creates an object for the left motor on channel 0
frc::XRPMotor m_left_motor{0};
// This creates an object for the right motor on channel 1
frc::XRPMotor m_right_motor{1};
};
Drivetrain.cpp Source File
Now that we've declared our function in the header file, it's time to define what it actually does in the source (.cpp
) file. The flow chart you created earlier is crucial here, as it provides a clear visual representation of the logic, making it easier to translate the planned steps into actual code.
We will add the ButtonDrive
function definition under Drivetrain::Periodic()
// This is the definition of our Button Drive function.
// The code inside the curly braces {} is what runs when we call this function.
void Drivetrain::ButtonDrive(bool forward, bool backward, bool turnLeft, bool turnRight)
{
}
Next, we will add the code to the ButtonDrive
function. This function will use an if-else if-else
statement to determine the robot's movement based on the button inputs. If you're unfamiliar with if-else
statements, refer to the [if-else statements guide](../../../CPP Docs/CPP_software_quick_reference/index.md#if-else-statements) for more details. We will be writing our code in between the {}
in Drivetrain::ButtonDrive
Here is the implementation:
// Check if the forward button is pressed
if (forward) {
m_left_motor.Set(1); // Drive forward
m_right_motor.Set(-1); // Drive forward (inverted)
}
// Check if the backward button is pressed
else if (backward) {
m_left_motor.Set(-1); // Drive backward
m_right_motor.Set(1); // Drive backward (inverted)
}
// Check if the turnLeft button is pressed
else if (turnLeft) {
m_left_motor.Set(-1); // Turn left
m_right_motor.Set(-1); // Turn left
}
// Check if the turnRight button is pressed
else if (turnRight) {
m_left_motor.Set(1); // Turn right
m_right_motor.Set(1); // Turn right
}
// If no buttons are pressed, stop the robot
else {
m_left_motor.Set(0.0); // Stop
m_right_motor.Set(0.0); // Stop
}
Your Drivetrain.cpp file should look like this.
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "subsystems/Drivetrain.h"
Drivetrain::Drivetrain() = default;
// This method will be called once per scheduler run
void Drivetrain::Periodic() {}
// This is the definition of our Button Drive function.
// The code inside the curly braces {} is what runs when we call this function.
void Drivetrain::ButtonDrive(bool forward, bool backward, bool turnLeft, bool turnRight)
{
// Check if the forward button is pressed
if (forward) {
m_left_motor.Set(1); // Drive forward
m_right_motor.Set(-1); // Drive forward (inverted)
}
// Check if the backward button is pressed
else if (backward) {
m_left_motor.Set(-1); // Drive backward
m_right_motor.Set(1); // Drive backward (inverted)
}
// Check if the turnLeft button is pressed
else if (turnLeft) {
m_left_motor.Set(-1); // Turn left
m_right_motor.Set(-1); // Turn left
}
// Check if the turnRight button is pressed
else if (turnRight) {
m_left_motor.Set(1); // Turn right
m_right_motor.Set(1); // Turn right
}
// If no buttons are pressed, stop the robot
else {
m_left_motor.Set(0.0); // Stop
m_right_motor.Set(0.0); // Stop
}
}
RobotContainer.h header File
We will now need to open RobotContainer.h
-
First, we need to tell our
RobotContainer
(the brain) where to find the blueprints for ourDrivetrain
, ourXboxController
, and theRunCommand
we'll use to connect them. We do this by including their header files at the top ofRobotContainer.h
. Including a header is like giving the brain the instruction manual for a specific part or tool before it can use it.#include "subsystems/Drivetrain.h"
#include <frc/XboxController.h>
#include <frc2/command/RunCommand.h> -
Next, we need to create the actual
Drivetrain
andXboxController
objects inside ourRobotContainer
. Think of this as giving the brain its own set of legs and ears to use. We'll declare these in theprivate
section to keep our code organized. For more details on the controller, see the Xbox Controller section in the WPILib tutorialprivate:
// Create an instance of our Drivetrain subsystem
Drivetrain m_drivetrain;
// Create an instance of the Xbox Controller on USB port 0
frc::XboxController m_controller{0};
Your Drivetrain.h file should look like this.
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <frc2/command/CommandPtr.h>
#include <frc2/command/button/CommandXboxController.h>
#include "Constants.h"
#include "subsystems/ExampleSubsystem.h"
#include "subsystems/Drivetrain.h"
#include <frc/XboxController.h>
#include <frc2/command/RunCommand.h>
/**
* This class is where the bulk of the robot should be declared. Since
* Command-based is a "declarative" paradigm, very little robot logic should
* actually be handled in the {@link Robot} periodic methods (other than the
* scheduler calls). Instead, the structure of the robot (including subsystems,
* commands, and trigger mappings) should be declared here.
*/
class RobotContainer {
public:
RobotContainer();
frc2::CommandPtr GetAutonomousCommand();
private:
// Replace with CommandPS4Controller or CommandJoystick if needed
frc2::CommandXboxController m_driverController{
OperatorConstants::kDriverControllerPort};
// The robot's subsystems are defined here...
ExampleSubsystem m_subsystem;
// Create an instance of our Drivetrain subsystem
Drivetrain m_drivetrain;
// Create an instance of the Xbox Controller on USB port 0
frc::XboxController m_controller{0};
void ConfigureBindings();
};
RobotContainer.cpp source File
The RobotContainer.cpp
is the file that brings the plan from the header file to life, creating the real connection between the driver's commands and the robot's movement.
-
Setting the Drivetrain's Default Job
We need to tell the
Drivetrain
what it should be doing by default: listening to our buttons. We do this by setting its "Default Command". This command will run automatically whenever no other special commands are using the drivetrain.In
RobotContainer.cpp
, find theConfigureBindings
function. This is where we'll add the code to link the controller to ourButtonDrive
function.// Set the default command for the drivetrain.
// This will run whenever no other command is running on the drivetrain.
m_drivetrain.SetDefaultCommand(frc2::RunCommand(
[this] {
// Define the buttons
bool forward = m_controller.GetYButton();
bool backward = m_controller.GetAButton();
bool left = m_controller.GetXButton();
bool right = m_controller.GetBButton();
// Drive with tank style
m_drivetrain.ButtonDrive(forward, backward, left, right);
},
{&m_drivetrain}));
Your Drivetrain.cpp file should look like this.
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "RobotContainer.h"
#include <frc2/command/button/Trigger.h>
#include "commands/Autos.h"
#include "commands/ExampleCommand.h"
RobotContainer::RobotContainer() {
// Initialize all of your commands and subsystems here
// Configure the button bindings
ConfigureBindings();
}
void RobotContainer::ConfigureBindings() {
// Set the default command for the drivetrain.
// This will run whenever no other command is running on the drivetrain.
m_drivetrain.SetDefaultCommand(frc2::RunCommand(
[this] {
// Define the buttons
bool forward = m_controller.GetYButton();
bool backward = m_controller.GetAButton();
bool left = m_controller.GetXButton();
bool right = m_controller.GetBButton();
// Drive with tank style
m_drivetrain.ButtonDrive(forward, backward, left, right);
},
{&m_drivetrain}));
// Configure your trigger bindings here
// Schedule `ExampleCommand` when `exampleCondition` changes to `true`
frc2::Trigger([this] {
return m_subsystem.ExampleCondition();
}).OnTrue(ExampleCommand(&m_subsystem).ToPtr());
// Schedule `ExampleMethodCommand` when the Xbox controller's B button is
// pressed, cancelling on release.
m_driverController.B().WhileTrue(m_subsystem.ExampleMethodCommand());
}
frc2::CommandPtr RobotContainer::GetAutonomousCommand() {
// An example command will be run in autonomous
return autos::ExampleAuto(&m_subsystem);
}
Time to test your code
Great job writing your first XRP code. it is time to test your code. Go to XRP Run Code to test your code
If everything is working correctly, you can now drive your XRP robot using the buttons
Next Steps
You've built a working button drive, but you might notice the robot moves or turns too quickly. The speeds are hardcoded as 1
and -1
, which makes them hard to adjust.
In the next tutorial, we'll learn how to "tune" these values by replacing them with named variables, making our code much cleaner and easier to adjust.
➡️ Continue to Tuning with Variables and Namespaces